from __future__ import annotations

"""
风控与规则归一化
----------------
职责：
- 白名单校验（交易所/交易对/订单类型）
- 交易所规则（最小名义金额、步进）归一化
- 名义金额/滑点限制检查

说明：
- 规则字段名称在不同交易所返回中存在差异，方法内部做了多键兼容
"""

from decimal import ROUND_FLOOR, Decimal
from typing import Dict, Tuple

from fastapi import HTTPException, status

from ..config import get_settings


def _is_allowed(value: str, allowed: list[str], *, transform=lambda x: x) -> bool:
    if not allowed:
        return True
    normalized = [transform(item) for item in allowed]
    if '*' in normalized:
        return True
    return transform(value) in normalized


class RiskError(Exception):
    def __init__(self, code: str, message: str) -> None:
        super().__init__(message)
        self.code = code
        self.message = message


def _decimal(rule: Dict, keys: Tuple[str, ...], default: Decimal = Decimal("0")) -> Decimal:
    for key in keys:
        value = rule.get(key)
        if value is None:
            continue
        return Decimal(str(value))
    return default


def floor_to_step(value: Decimal, step: Decimal) -> Decimal:
    if step <= 0:
        return value
    quotient = (value / step).to_integral_value(rounding=ROUND_FLOOR)
    return quotient * step


def get_amount_step(rule: Dict) -> Decimal:
    return _decimal(
        rule,
        ("amount_step", "order_step", "step_size", "quantity_increment", "qty_step", "quantity_step"),
        Decimal("0"),
    )


def get_min_base_amount(rule: Dict) -> Decimal:
    return _decimal(
        rule,
        (
            "min_base_amount",
            "min_order_amount",
            "min_amount",
            "min_order_size",
            "min_quantity",
            "base_min_size",
            "min_qty",
        ),
        Decimal("0"),
    )


def get_reference_price(rule: Dict) -> Decimal:
    return _decimal(
        rule,
        (
            "price",
            "reference_price",
            "mid_price",
            "average_price",
            "avg_price",
            "last_price",
        ),
        Decimal("0"),
    )


def enforce_whitelists(connector: str, symbol: str, order_type: str) -> None:
    settings = get_settings()
    if connector and not _is_allowed(connector, settings.allowed_connectors_list, transform=lambda x: x.lower()):
        raise RiskError("ERR_CONNECTOR", f"Connector {connector} not allowed")
    if not _is_allowed(symbol, settings.allowed_symbols_list, transform=lambda x: x.upper()):
        raise RiskError("ERR_SYMBOL", f"Symbol {symbol} not allowed")
    if not _is_allowed(order_type, settings.allowed_order_types_list, transform=lambda x: x.upper()):
        raise RiskError("ERR_ORDER_TYPE", f"Order type {order_type} not allowed")


def apply_trading_rules(
    *, rule: Dict, notional: Decimal, price: Decimal | None = None
) -> Tuple[Decimal, Decimal | None]:
    min_notional = _decimal(
        rule,
        ("min_notional", "min_order_value", "min_quote_amount", "min_base_amount"),
        Decimal("0"),
    )
    if notional < min_notional:
        raise RiskError("ERR_MIN_NOTIONAL", "Notional below exchange minimum")

    amount_step = get_amount_step(rule)
    if price is None:
        price = _decimal(rule, ("price", "price_step", "tick_size"), Decimal("0"))
    normalized_notional = floor_to_step(notional, amount_step or Decimal("0.00000001"))
    if normalized_notional <= 0:
        raise RiskError("ERR_MIN_NOTIONAL", "Notional too small after normalization")
    return normalized_notional, price


def enforce_max_notional(notional: Decimal, max_notional: Decimal | None) -> None:
    settings = get_settings()
    limit = Decimal(str(max_notional if max_notional is not None else settings.max_notional_default))
    if notional > limit:
        raise RiskError("ERR_MAX_NOTIONAL", "Notional exceeds configured limit")


def check_slippage(expected_price: Decimal, market_price: Decimal, slippage_bps: int) -> None:
    if slippage_bps <= 0:
        return
    diff = abs(expected_price - market_price)
    if expected_price == 0:
        return
    bps = (diff / expected_price) * Decimal("10000")
    if bps > Decimal(slippage_bps):
        raise RiskError("ERR_SLIPPAGE", "Price slippage exceeds protection threshold")
